﻿using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace InfiniTec.DirectoryServices
{
    public sealed class CrossForestTranslator
    {
        private readonly Connection _Connection;
        private TranslationFlags _TranslationFlags;
        private NameFormat _OutputFormat;
        private Dictionary<string, Connection> _DomainSpecificConnections;
        
        /// <summary>
        /// Gets or sets the translation flags.
        /// </summary>
        /// <value>The translation flags.</value>
        public TranslationFlags TranslationFlags
        {
            [DebuggerStepThrough]
            get { return _TranslationFlags; }
            [DebuggerStepThrough]
            set { _TranslationFlags = value; }
        }

        /// <summary>
        /// Gets or sets the output format.
        /// </summary>
        /// <value>The output format.</value>
        public NameFormat OutputFormat
        {
            get { return _OutputFormat; }
            set { _OutputFormat = value; }
        }


        public CrossForestTranslator(Connection connection)
        {
            _Connection = connection;
        }

        public IEnumerable<CrossForestTranslationResult> Translate(IEnumerable<string> names)
        {
            Dictionary<string, List<CrossForestTranslationResult>> unresolvedEntities =
                new Dictionary<string, List<CrossForestTranslationResult>>();

            foreach (CrossForestTranslationResult result in Translate(names, _Connection))
            {
                if (result.Status != TranslationStatus.NotFound)
                {
                    yield return result;
                }
                else
                {
                    RegisterAccount(result, unresolvedEntities);
                }
            }

            if (unresolvedEntities.Count == 0)
            {
                yield break;
            }

            if (_DomainSpecificConnections == null)
            {
                _DomainSpecificConnections = new Dictionary<string, Connection>(StringComparer.OrdinalIgnoreCase);
                foreach (
                    DomainTrust trust in
                        DomainTrustCollection.EnumerateDomainTrusts(TrustFlags.DirectInbound | TrustFlags.DirectOutbound)
                    )
                {
                    _DomainSpecificConnections[trust.NetBiosName] = new Connection(trust.Name,
                                                                                   DirectoryIdentifierType.DnsDomain,
                                                                                   false, _Connection.Credential,
                                                                                   _Connection.AuthenticationType);
                }
            }

            foreach (KeyValuePair<string, List<CrossForestTranslationResult>> pair in unresolvedEntities)
            {
                Connection connection;
                if (!_DomainSpecificConnections.TryGetValue(pair.Key, out connection))
                {
                    foreach (CrossForestTranslationResult result in pair.Value)
                    {
                        yield return result;
                    }
                }
                foreach (
                    CrossForestTranslationResult result in Translate(pair.Value.ConvertAll<string>(delegate(CrossForestTranslationResult input) { return input.InputName; }), connection))
                {
                    yield return result;
                }
            }
        }

        private static void RegisterAccount(CrossForestTranslationResult translationResult, IDictionary<string, List<CrossForestTranslationResult>> entities)
        {
            List<CrossForestTranslationResult> accounts;

            string domain = GetDomain(translationResult.InputName);
            if (!entities.TryGetValue(domain, out accounts))
            {
                accounts = new List<CrossForestTranslationResult>();
                entities[domain] = accounts;
            }
            if (!accounts.Contains(translationResult))
            {
                accounts.Add(translationResult);
            }
        }

        private static string GetDomain(string accountName)
        {
            if (string.IsNullOrEmpty(accountName)) throw new ArgumentNullException(accountName);
            int index = accountName.IndexOf("\\");
            if (index == -1)
            {
                throw new ArgumentException("The specified account has no domain information");
            }
            return accountName.Substring(0, index);
        }

        private IEnumerable<CrossForestTranslationResult> Translate(IEnumerable<string> names, Connection connection)
        {
            Translator translator = new Translator(connection);
            translator.TranslationFlags = new TranslationFlags();
            translator.InputFormat = NameFormat.NT4AccountName;
            translator.OutputFormat = OutputFormat;

            foreach (TranslationResult result in translator.Translate(names))
            {
                yield return new CrossForestTranslationResult(result, connection);
            };

        }
        public IEnumerable<CrossForestTranslationResult> Translate(params string[] names)
        {
            return Translate((IEnumerable<string>) names);
        }
    }
}
